package executor

import (
	"context"
	"regexp"
	"strings"

	"github.com/glennliao/apijson-go/config"
	"github.com/glennliao/apijson-go/consts"
	"github.com/glennliao/apijson-go/model"
	"github.com/glennliao/apijson-go/query"
	"github.com/glennliao/apijson-go/util"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/samber/lo"
)

type S数据库解析器 func(ctx context.Context) gdb.DB

type SQL执行器 struct {
	S上下文 context.Context

	J角色 string

	// 保存where条件 [ ["user_id",">", 123], ["user_id","<=",345] ]
	T条件   [][]any
	F访问条件 map[string][]any

	L列名 []string
	P排序 string
	F分组 string

	// 是否最终为空结果, 用于node中中断数据获取
	S是否空结果 bool

	P配置 *config.Z执行器配置

	S数据库解析器 S数据库解析器
}

func X新建执行器(ctx context.Context, P配置 *config.Z执行器配置) (query.C查询执行器接口, error) {
	return &SQL执行器{
		S上下文:   ctx,
		T条件:    [][]any{},
		L列名:    nil,
		P排序:    "",
		F分组:    "",
		S是否空结果: false,
		P配置:    P配置,
		S数据库解析器: func(ctx context.Context) gdb.DB {
			return g.DB()
		},
	}, nil
}

// 解析条件 解析查询条件
// 访问验证 内部调用时, 不校验是否可使用该种查询方式
func (Z执行器 *SQL执行器) J解析条件(条件 model.MapStrAny, 访问验证 bool) error {
	for 键, 条件值 := range 条件 {
		switch {
		case strings.HasSuffix(键, consts.C操作符包含):
			Z执行器.J解析批量条件(util.Y移除后缀(键, consts.C操作符包含), 条件值)

		case strings.HasSuffix(键, consts.C操作符模糊匹配):
			Z执行器.T条件 = append(Z执行器.T条件, []any{键[0 : len(键)-1], consts.SQL模糊匹配, gconv.String(条件值)})

		case strings.HasSuffix(键, consts.C操作符正则):
			Z执行器.T条件 = append(Z执行器.T条件, []any{键[0 : len(键)-1], consts.SQL正则, gconv.String(条件值)})

		case 键 == consts.Y原始数据 && !访问验证:
			Z执行器.F访问条件 = 条件值.(map[string][]any)

		default:
			Z执行器.T条件 = append(Z执行器.T条件, []any{键, consts.SQL等于, 条件值})
		}
	}

	if !访问验证 {
		return nil
	}

	if Z执行器.P配置.W无需验证 { // 可任意字段搜索
		return nil
	}

	输入字段映射 := Z执行器.P配置.G根据角色获取输入字段映射()

	数据库字段风格 := Z执行器.P配置.S数据库字段风格

	表名 := Z执行器.P配置.H获取表名()

	for _, 条件 := range Z执行器.T条件 {
		字段 := 数据库字段风格(Z执行器.S上下文, 表名, 条件[0].(string))
		if 允许操作, 存在 := 输入字段映射[字段]; 存在 {
			if len(允许操作) == 0 {
				continue
			}

			if 允许操作[0] == "*" {
				continue
			}

			操作符 := 条件[1].(string)
			if 操作符 == consts.SQL模糊匹配 {
				条件值 := 条件[2].(string)
				操作符 = consts.C操作符模糊匹配
				if strings.HasPrefix(条件值, "%") {
					操作符 = "%" + 操作符
				}
				if strings.HasSuffix(条件值, "%") {
					操作符 = 操作符 + "%"
				}
			}

			if !lo.Contains(允许操作, 操作符) {
				return consts.X新建请求验证错误("不允许使用" + 条件[0].(string) + "的搜索方式:" + 操作符)
			}

		} else {
			return consts.X新建请求验证错误("不允许使用" + 条件[0].(string) + "搜索")
		}
	}

	return nil
}

// 解析批量条件 解析批量查询条件
func (执行器 *SQL执行器) J解析批量条件(键 string, 条件值 any) {
	var 条件列表 [][]string
	var 值 = 条件值

	if 字符串值, 是字符串 := 条件值.(string); 是字符串 {
		for _, 条件项 := range strings.Split(字符串值, ",") {
			var 项 []string
			操作符列表 := []string{"<=", "<", ">=", ">"}
			是否等于 := true
			for _, 操作符 := range 操作符列表 {
				if strings.HasPrefix(条件项, 操作符) {
					项 = append(项, 操作符, 条件项[len(操作符):])
					是否等于 = false
					break
				}
			}
			if 是否等于 {
				项 = append(项, " = ", 条件项)
			}
			条件列表 = append(条件列表, 项)
		}
		值 = 条件列表
	}

	获取键 := func(键 string) string {
		return 键[0 : len(键)-1]
	}

	switch 键[len(键)-1] {
	case '&', '|', '!':
		执行器.T条件 = append(执行器.T条件, []any{获取键(键), 键[len(键)-1], 值})
	default:
		执行器.T条件 = append(执行器.T条件, []any{键, "in", 值})
	}
}

var 字段正则 = regexp.MustCompile(`^[\s\w][\w()]+`) // 匹配 field, COUNT(field)

// 解析控制 解析 @column,@group等控制类
func (执行器 *SQL执行器) J解析控制(控制参数 model.Map) error {
	字段风格 := 执行器.P配置.S数据库字段风格
	表名 := 执行器.P配置.H获取表名()
	for 键, 值 := range 控制参数 {
		// 使用;分割字段
		字段字符串 := strings.ReplaceAll(gconv.String(值), ";", ",")

		字段列表 := strings.Split(字段字符串, ",")

		for i, 字段 := range 字段列表 {
			字段列表[i] = 字段正则.ReplaceAllStringFunc(字段, func(字段 string) string {
				return 字段风格(执行器.S上下文, 表名, 字段)
			}) // 将请求字段转化为数据库字段风格
		}

		switch 键 {
		case "@column":
			执行器.L列名 = 字段列表

		case "@order":
			字段字符串 = strings.Join(字段列表, ",")
			排序 := strings.ReplaceAll(字段字符串, "-", " DESC")
			排序 = strings.ReplaceAll(排序, "+", " ")
			执行器.P排序 = 排序

		case "@group":
			字段字符串 = strings.Join(字段列表, ",")
			执行器.F分组 = 字段字符串
		}
	}

	return nil
}

func (执行器 *SQL执行器) G构建查询模型() *gdb.Model {
	表名 := 执行器.P配置.H获取表名()
	模型 := 执行器.S数据库解析器(执行器.S上下文).Model(表名).Ctx(执行器.S上下文)

	if 执行器.P排序 != "" {
		模型 = 模型.Order(执行器.P排序)
	}

	条件构建器 := 模型.Builder()

	字段风格 := 执行器.P配置.S数据库字段风格

	for _, 条件项 := range 执行器.T条件 {
		字段 := 字段风格(执行器.S上下文, 表名, 条件项[0].(string))
		操作符 := 条件项[1]
		值 := 条件项[2]

		if 多条件, 是多条件 := 值.([][]string); 是多条件 {
			switch 操作符 {
			case '&':
				构建器 := 模型.Builder()
				for _, 条件 := range 多条件 {
					构建器 = 构建器.Where(字段+" "+条件[0], 条件[1])
				}
				条件构建器 = 条件构建器.Where(构建器)

			case '|':
				构建器 := 模型.Builder()
				for _, 条件 := range 多条件 {
					构建器 = 构建器.WhereOr(字段+" "+条件[0], 条件[1])
				}
				条件构建器 = 条件构建器.Where(构建器)

			case '!':
				条件构建器 = 条件构建器.WhereNotIn(字段, 多条件)

			default:
				条件构建器 = 条件构建器.WhereIn(字段, 多条件)
			}
		} else {
			switch 操作符 {
			case consts.SQL模糊匹配:
				条件构建器 = 条件构建器.WhereLike(字段, 值.(string))
			case consts.SQL正则:
				条件构建器 = 条件构建器.Where(字段+" "+consts.SQL正则, 值.(string))
			case "in":
				条件构建器 = 条件构建器.WhereIn(字段, 值)
			case consts.SQL等于:
				条件构建器 = 条件构建器.Where(字段, 值)
			}
		}
	}

	模型 = 模型.Where(条件构建器)
	if 执行器.F访问条件 != nil {
		for 字段, 条件 := range 执行器.F访问条件 {
			模型 = 模型.Where(字段, 条件...)
		}
	}

	if 执行器.F分组 != "" {
		模型 = 模型.Group(执行器.F分组)
	}

	return 模型
}

func (执行器 *SQL执行器) H获取查询字段() []string {
	可访问字段 := 执行器.P配置.G根据角色获取输出字段列表()
	表名 := 执行器.P配置.H获取表名()

	var 字段列表 []string

	if 执行器.L列名 != nil {
		字段列表 = 执行器.L列名
	} else {
		字段列表 = 执行器.P配置.H获取表字段列表()
	}

	var 查询字段 = make([]string, 0, len(字段列表))

	json字段风格 := 执行器.P配置.JSON字段风格
	数据库字段风格 := 执行器.P配置.S数据库字段风格

	for _, 字段 := range 字段列表 {
		字段名 := 字段
		字段 = strings.ReplaceAll(字段, ":", " AS ")
		if !strings.Contains(字段, " AS ") {
			json字段 := json字段风格(执行器.S上下文, 表名, 字段)
			if json字段 != 字段 {
				字段 = "`" + 字段 + "`" + " AS " + json字段
			} else {
				字段 = "`" + 字段 + "`"
			}
		} else {
			字段名 = strings.TrimSpace(strings.Split(字段名, "AS")[0])
		}

		// 过滤可访问字段
		if 执行器.P配置.W无需验证 || lo.Contains(可访问字段, 数据库字段风格(执行器.S上下文, 表名, 字段名)) ||
			len(可访问字段) == 0 /* 数据库中未设置, 则看成全部可访问 */ {
			查询字段 = append(查询字段, 字段)
		}
	}

	return 查询字段
}

func (执行器 *SQL执行器) S设置空结果() {
	执行器.S是否空结果 = true
}

func (执行器 *SQL执行器) C查询总数() (总数 int64, 错误 error) {
	模型 := 执行器.G构建查询模型()
	数量, 错误 := 模型.Count()
	if 错误 != nil || 数量 == 0 {
		return 0, 错误
	} else {
		总数 = int64(数量)
	}

	return 总数, nil
}

func (执行器 *SQL执行器) C查询列表(页码 int, 数量 int) (列表 []model.Map, 错误 error) {
	if 执行器.S是否空结果 {
		return nil, 错误
	}

	模型 := 执行器.G构建查询模型()
	模型 = 模型.Fields(执行器.H获取查询字段())
	模型 = 模型.Page(页码, 数量)

	结果集, 错误 := 模型.All()
	if 错误 != nil {
		return nil, 错误
	}

	for _, 记录 := range 结果集.List() {
		列表 = append(列表, 记录)
	}

	return 列表, nil
}

func (执行器 *SQL执行器) C查询单条() (model.Map, error) {
	if 执行器.S是否空结果 {
		return nil, nil
	}

	模型 := 执行器.G构建查询模型()
	模型 = 模型.Fields(执行器.H获取查询字段())

	单条记录, 错误 := 模型.One()
	return 单条记录.Map(), 错误
}
